package com.zhss.dfs.namenode.server;

import java.util.LinkedList;

/**
 * 负责管理edits log日志的核心组件
 *
 * @author zhonghuashishan
 */
public class FSEditlog2 {

    //当前写入的logId
    private Long logId = 0L;

    //内存双缓冲区
    private DoubleBuffer doubleBuffer = new DoubleBuffer();

    //是否正在写入,要保证可见性
    private volatile Boolean isSyncing = false;

    //是否需要等待写入，要保证可见性
    private volatile Boolean isWaitSync = false;

    //在同步到磁盘中的最大的一个id
    private Long maxId = 0L;

    //每个线程保存自己的当前写入logId副本
    private ThreadLocal<Long> localId = new ThreadLocal<Long>();


    /**
     * 多线程写入editLog
     *
     * @param content
     */
    public void writeLog(String content) {
        synchronized (content) {
            logId = logId++;
            //构造一条editLog，生成txid,需要保证线程安全
            EditLog editLog = new EditLog(logId, content);

            //id存入本地，同步的时候判断从哪个开始
            localId.set(logId);

            //写入缓存
            doubleBuffer.write(editLog);
        }

        this.syncBuffer();



    }


    /**
     * 同步buffer到磁盘
     */
    public void syncBuffer() {
        synchronized (this) {
            if (isSyncing) {
                // * 数据刷到磁盘，可以批量刷。
                // * 即当前全局txid=5,内存缓冲数据是 2，3，4，5，则可以把这部分数据一次性全部刷入，此时的状态是刷入中。
                // * 刷入后保存刷入maxId
                Long id = localId.get();

                //判断当前id和maxId大小，如果id<=maxId证明已经刷入了，直接返回；否则可以刷入;
                if (id <= maxId) {
                    return;
                }

                //在刷入前，需要判断isWaitSync和isSyncing状态。
                //如果是等待刷入，那就不刷直接返回，比如id=10
                //如果是正在刷入，那就不好说了，可能是上一次正在写入，那么就需要等待写入完成（isSyncing=false），并且释放锁
                // 比如：正在写入的id=5，此时id=6，很可能就没刷完，因为比较慢。
                if (isWaitSync) {
                    return;
                }
                while (isSyncing) {
                    try {
                        wait(1000);
                        isWaitSync = true;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
                //此时写入已经完成，可以写入了
                isWaitSync = false;
            }
            //交换缓冲区
            doubleBuffer.setReadyToBuffer();
            //更新当前同步maxId
            maxId=doubleBuffer.getSyncMaxTxid();
            //设置同步状态，接下来要刷入磁盘
            isSyncing=true;
        }
        //刷入本地磁盘
        doubleBuffer.flush();

        //刷盘完后更新状态
        synchronized (this){
            isSyncing=false;
            //唤醒等待得线程写入数据
            notifyAll();
        }


    }


    /**
     * 一条editLog结构
     */
    class EditLog {
        private Long logId;

        private String content;

        public EditLog(Long logId, String content) {
            this.logId = logId;
            this.content = content;
        }
    }

    class DoubleBuffer {
        //当前线程写入的editLog
        LinkedList<EditLog> currentLogBuffer = new LinkedList<EditLog>();

        //缓冲区的buffer
        LinkedList<EditLog> buffer = new LinkedList<EditLog>();


        /**
         * 写入一条editLog到内存
         */
        public void write(EditLog log) {
            currentLogBuffer.add(log);
        }


        /**
         * 写入缓冲区
         */
        public void setReadyToBuffer() {
            LinkedList<EditLog> temp = currentLogBuffer;
            buffer = temp;
            currentLogBuffer.clear();
        }

        /**
         * 获取buffer缓冲区里的最大的一个txid
         * @return
         */
        public Long getSyncMaxTxid() {
            return buffer.getLast().logId;
        }

        /**
         * 刷入本地磁盘
         */
        public void flush() {
            for (EditLog editLog : buffer) {
                System.out.println("将edit log写入磁盘文件中：" + editLog);
            }
        }
    }

}
